var ctx = document.getElementById('c').getContext('2d'), originalCanvasData, canvasData;
var img = new Image();
img.src = '/images/img_1.jpg';
img.onload = function () {
	ctx.canvas.width = this.naturalWidth;
	ctx.canvas.height = this.naturalHeight;
	ctx.drawImage(this, 0, 0);
	originalCanvasData = ctx.getImageData(0, 0, this.naturalWidth, this.naturalHeight);
	canvasData = ctx.getImageData(0, 0, this.naturalWidth, this.naturalHeight);
}
/**
 * 全局参数
 */
var curAngle = 0;  // 当前旋转的角度

/**
 * 显示原图
 */
function originalBtnClick () {
	original(originalCanvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function original (imgData) {
	ergodicPixel(imgData, function (idx, rgba) {
		canvasData.data[idx + 0] = rgba.r;
		canvasData.data[idx + 1] = rgba.g;
		canvasData.data[idx + 2] = rgba.b;
		canvasData.data[idx + 3] = rgba.a;
	})
}

/* --============================================================================================================================= */
/* --=======================================================灰度处理=============================================================== */
/* --============================================================================================================================= */
// 灰度值本质上是三色分量相同的一个特殊情况，可取范围为0-255,0位白色，255位黑色
/**
 * 加权灰度处理
 * 为rgb三色分量进行加权计算，根据人眼对色彩敏感度确定加权值（绿色敏感，蓝色不敏感）
 */
function weightingGrayscaleBtnClick () {
	weightingGrayscal(originalCanvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function weightingGrayscal (imgData) {
	ergodicPixel(imgData, function (idx, rgba) {
		var avg = ( 0.2126 * rgba.r + 0.7152 * rgba.g + 0.0722 * rgba.b);
		canvasData.data[idx + 0] = avg;
		canvasData.data[idx + 1] = avg;
		canvasData.data[idx + 2] = avg;
		canvasData.data[idx + 3] = rgba.a;
	})
}
/**
 * 分量灰度化
 * 	将彩色图像中的三分量的亮度作为灰度图的灰度值
 */
// 红色分量灰度化
function rWeightGrayscaleBtnClick () {
	weightGrayscal(originalCanvasData, 'red');
	ctx.putImageData(canvasData, 0, 0);
}
// 绿色分量灰度化
function gWeightGrayscaleBtnClick () {
	weightGrayscal(originalCanvasData, 'green');
	ctx.putImageData(canvasData, 0, 0);
}
// 蓝色分量灰度化
function bWeightGrayscaleBtnClick () {
	weightGrayscal(originalCanvasData, 'blue');
	ctx.putImageData(canvasData, 0, 0);
}
/**
 * 根据分量类型返回用改分量灰度化后的图像数据
 * @param {CanvasImgData} imgData canvas图像数据
 * @param {String} type 需要的分量类型
 */
function weightGrayscal (imgData, type) {
	ergodicPixel(imgData, function (idx, rgba) {
		var grayValue;
		switch (type) {
			case 'red': grayValue = rgba.r; break;
			case 'green': grayValue = rgba.g; break;
			case 'blue': grayValue = rgba.b; break;
			default: grayValue = 175; break;
		}
		canvasData.data[idx + 0] = grayValue;
		canvasData.data[idx + 1] = grayValue;
		canvasData.data[idx + 2] = grayValue;
		canvasData.data[idx + 3] = rgba.a;
	})
}
/**
 * 最大值灰度化
 * 	取rgb三色分量重最大那一个分量作为灰度值（效果与分量灰度化中某一种相同）
 */
function maxWeightGrayscaleBtnClick () {
	maxWeightGrayscale(originalCanvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function maxWeightGrayscale (imgData) {
	ergodicPixel(imgData, function (idx, rgba) {
		var grayValue = Math.max(rgba.r, rgba.g, rgba.b);
		canvasData.data[idx + 0] = grayValue
		canvasData.data[idx + 1] = grayValue
		canvasData.data[idx + 2] = grayValue
		canvasData.data[idx + 3] = rgba.a;
	})
}
/**
 * 平均值灰度化
 * 	取像素三色分量平均值作为灰度值
 */
function avgGrayscaleBtnClick () {
	avgGrayscale(originalCanvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function avgGrayscale (imgData) {
	ergodicPixel(imgData, function (idx, rgba) {
		var grayValue = (rgba.r + rgba.b + rgba.g) / 3;
		canvasData.data[idx + 0] = grayValue;
		canvasData.data[idx + 1] = grayValue;
		canvasData.data[idx + 2] = grayValue;
		canvasData.data[idx + 3] = rgba.a;
	})
}


/* --============================================================================================================================= */
/* --=======================================================旋转处理=============================================================== */
/* --============================================================================================================================= */
/**
 * 旋转处理
 */
function rotateBtnClick () {
	curAngle += 90;
	if(curAngle === 360) {
		curAngle = 0;
	}
	rotateCanvas(curAngle);
}
function rotateCanvas (angle) {
	var tempCtx = document.createElement('canvas').getContext('2d');
	tempCtx.canvas.width = img.width;
	tempCtx.canvas.height = img.height;

	switch (angle) {
		case 90: tempCtx.translate(tempCtx.canvas.width, 0); break;
		case 180: tempCtx.translate(tempCtx.canvas.width, tempCtx.canvas.height); break;
		case 270: tempCtx.translate(0, tempCtx.canvas.height); break;
		case 0: tempCtx.translate(0, 0); break;
		default: break;
	}
	tempCtx.rotate(angle * Math.PI / 180);

	if(angle === 180 || angle === 0) {
		tempCtx.drawImage(img, 0, 0);
	} else {
		var whr = 242 / 160;
		var hwr = 160 / 242;
		tempCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, tempCtx.canvas.width * hwr, tempCtx.canvas.height * whr);
	}

	ctx.drawImage(tempCtx.canvas, 0, 0);
}


/* --============================================================================================================================= */
/* --=======================================================亮度处理=============================================================== */
/* --============================================================================================================================= */
/**
 * 范围条控件处理函数
 * 将rgb值加上选取的偏移量
 */
function handleBrightnessRange () {
	brightness(+this.value, originalCanvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function brightness (offset, imgData) {
	ergodicPixel(imgData, function (idx, rgba) {
		canvasData.data[idx + 0] = rgba.r + offset;
		canvasData.data[idx + 1] = rgba.g + offset;
		canvasData.data[idx + 2] = rgba.b + offset;
		canvasData.data[idx + 3] = rgba.a;
	});
}


/* --============================================================================================================================= */
/* --=======================================================二值化处理============================================================= */
/* --============================================================================================================================= */
/**
 * 二值化
 * 	按特定阈值 T 将图像像素分成黑白两色，按照 T 的取值方式可分为固定二值化和自适应二值化。自适应二值化中又涉及不同的 T 的算法。
 * 	按照二值化算法作用范围可分为全局二值化和局部二值化，全局二值化对细节的保留很差，特别是因光线问题导致均值下降而产生死黑问题。
 *  往往使用局部二值化，与T值算法结合形成完善的 自适应局部二值化 算法
 */
function avgTwoValueBtnClick () {
	weightingGrayscal(originalCanvasData); // 灰度化
	avgTwoValue(canvasData);
	ctx.putImageData(canvasData, 0, 0);
}
// 全局 单像素 均值 二值化 （不同的二值化方法区别在于对阈值T的算法）
function avgTwoValue (imgData) {
	ergodicPixel(imgData, function (idx, rgba) {
		var T = StableT(); // 阈值为一个固定值
		var avgValue = (rgba.r + rgba.g + rgba.b) / 3;
		judgeAndSet(avgValue, T, idx, rgba);
	})
}
/**
 * 全局二值化
 * 	根据更灵活的阈值算法进行二值化
 */
function allTwoValueBtnClick () {
	weightingGrayscal(originalCanvasData); // 灰度化
	allTwoValue(canvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function allTwoValue (imgData) {
	var T = getAvgImgData(imgData); // 阈值是图像均值（固定）
	ergodicPixel(imgData, function (idx, rgba) {
		var avgValue = (rgba.r + rgba.g + rgba.b) / 3;
		judgeAndSet(avgValue, T, idx, rgba);
	})
}
/**
 * 自适应二值化（Wellnel二值化算法）
 * 	阈值算法是每个像素点的阈值来自于同行同列所有像素点的均值
 */
function crossTwoValueBtnClick () {
	weightGrayscal(originalCanvasData);
	crossTwoValue(originalCanvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function crossTwoValue (imgData) {
	var s = Math.floor(imgData.width / 8), t = 15; // Wellnel二值化算法的系数
	var integralImg = []; // 维护一个列表，元素是矩形左上角所有像素的像素值和
	var i, j, sum, count, index, x1, y1, x2, y2, s2 = Math.ceil(s / 2);
	// 初始化像素值和列表
	for(i = 0; i < imgData.width; i += 1) {
		// 重置列像素和
		sum = 0;
		for(j = 0; j < imgData.height; j += 1) {
			index = i + j * imgData.width; // 像素下标
			sum += getPixelByIndex(imgData, index);
			if(i === 0) {
				integralImg[index] = sum;
			} else {
				integralImg[index] = integralImg[index - 1] + sum;
			}
		}
	}
	ergodicPixel(imgData, function (idx, rgba, x, y) {
		var index = x + y * imgData.width;
		// 设置一个SxS范围矩形
		x1 = x - s2; y1 = y - s2;
		x2 = x + s2; y2 = y + s2;
		// 边界控制
		if(x1 < 0) x1 = 0;
		if(x2 >= imgData.width) x2 = imgData.width - 1;
		if(y1 < 0) y1 = 0;
		if(y2 >= imgData.height) y2 = imgData.height - 1;
		// 计数
		count = (x2 - x1) * (y2 - y1);
		// SxS 范围矩形图像像素和
		sum = integralImg[y2*imgData.width+x2] -  
			integralImg[y1*imgData.width+x2] -  
			integralImg[y2*imgData.width+x1] +  
			integralImg[y1*imgData.width+x1];
		// 阈值
		var T = sum * (100 - t) / 100;
		// 范围内中心像素值之和
		var avgValue = getPixelByIndex(imgData, index) * count;
		judgeAndSet(avgValue, T, idx, rgba);
	})
}
// 下面是一些阈值获取算法
/**
 * 固定阈值
 */
function StableT () {
	return 128;
}
/**
 * 获取整个图像均值
 * @param {CanvasImgData} imgData canvas图像数据
 */
function getAvgImgData (imgData) {
	var total = 0, count = 0;
	ergodicPixel(imgData, function (idx, rgba) {
		total += (rgba.r + rgba.g + rgba.b) / 3;
		count++;
	})
	return total / count;
}


/* --============================================================================================================================= */
/* --======================================================饱和度处理============================================================== */
/* --============================================================================================================================= */
function saturationBarChange () {
	var newS = +((+this.value / 100)).toFixed(2);
	saturation(originalCanvasData, newS);
	ctx.putImageData(canvasData, 0, 0);
}
function saturation (imgData, newS) {
	ergodicPixel(imgData, function (idx, rgba) {
		var hsl = HSL_RGB.RGBtoHSL(rgba.r, rgba.g, rgba.b);
		hsl.s = newS;
		var newRgb = HSL_RGB.HSLtoRGB(hsl.h, hsl.s, hsl.l);
		canvasData.data[idx + 0] = newRgb.r;
		canvasData.data[idx + 1] = newRgb.g;
		canvasData.data[idx + 2] = newRgb.b;
		canvasData.data[idx + 3] = rgba.a;
	})
}


/* --============================================================================================================================= */
/* --======================================================对比度处理============================================================== */
/* --============================================================================================================================= */
function contrastRatioBarChange () {
	contrastRatio(originalCanvasData, +this.value / 25);
	ctx.putImageData(canvasData, 0, 0);
}
function contrastRatio (imgData, ratio) {
	console.log(ratio);
	ergodicPixel(imgData, function (idx, rgba) {
		var l = ( 0.2126 * rgba.r + 0.7152 * rgba.g + 0.0722 * rgba.b) / 255, newL = 0;
		var hsv = HSV_RGB.RGBtoHSV(rgba.r, rgba.g, rgba.b);
		/**
		 * ps的对比度算法是基于明度的，在Lab色彩空间下调节对比度是L通道发生变化，但 a 和 b 通道是没有改变的。
		 * 但 Lab 与 rgb 转化复杂，时间空间复杂度较高，我们可以使用 HSV 色彩空间代替Lab。
		 * 算法很简单，根据
		 */
		if(l > .5) {
			newL = (l - .5) * (l - 1) * ratio + l;
		} else if (l < .5) {
			newL = -(l - .5) * (l - 0) * ratio + l;
		}
		var rgb = HSV_RGB.HSVtoRGB(hsv.h, hsv.s, newL);
		canvasData.data[idx + 0] = rgb.r;
		canvasData.data[idx + 1] = rgb.g;
		canvasData.data[idx + 2] = rgb.b;
		canvasData.data[idx + 3] = rgba.a;
	})
}


/* --============================================================================================================================= */
/* --=======================================================其他处理=============================================================== */
/* --============================================================================================================================= */
/**
 * 反色处理
 * 255 减去原来的rgb值得到反色rgb值
 */
function anticolorBtnClick () {
	anticolor(originalCanvasData);
	ctx.putImageData(canvasData, 0, 0);
}
function anticolor (imgData) {
	ergodicPixel(imgData, function (idx, rgba) {
		canvasData.data[idx + 0] = 255 - rgba.r;
		canvasData.data[idx + 1] = 255 - rgba.g;
		canvasData.data[idx + 2] = 255 - rgba.b;
		canvasData.data[idx + 3] = rgba.a;
	});
}

/**
 * 简单的膨胀算法
 * 膨胀是将与物体接触的全部背景点合并到该物体中，使边界向外部扩张的过程。
 * 能够用来填补物体中的空洞
 * 
 * 判断一个像素周围存在黑色像素点，就设置自己为黑色
 */
function simpleExpandBtnClick() {
	weightingGrayscal(originalCanvasData); // 灰度化
	allTwoValue(canvasData); // 使用全局二值化算法
	const valueDataArray = new ImageData(canvasData.width, canvasData.height);
	ergodicPixel(canvasData, function (idx, rgba, x, y) {
		valueDataArray.data[idx + 0] = rgba.r;
		valueDataArray.data[idx + 1] = rgba.g;
		valueDataArray.data[idx + 2] = rgba.b;
		valueDataArray.data[idx + 3] = rgba.a;
		// 注释上面四句代码可进行黑色区域外描边
		if (rgba.r === 255) {
			const around = getAroundPixel(canvasData, x, y);
			for(let i = 0; i < around.length; i += 1) {
				if (around[i] && around[i].r === 0) {
					valueDataArray.data[idx + 0] = 0;
					valueDataArray.data[idx + 1] = 0;
					valueDataArray.data[idx + 2] = 0;
					valueDataArray.data[idx + 3] = 255;
					break;
				}
			}
		}
	});
	ctx.putImageData(valueDataArray, 0, 0);
}

/**
 * 简单腐蚀算法
 * 腐蚀是一种消除边界点，使边界向内部收缩的过程。能够用来消除小且无意义的物体。
 * 
 * 一次遍历图像中每个像素，检查它四周的八个像素，假设有白色的像素，则设置改点为白色
 */
function simpleCorrosionClick() {
	weightingGrayscal(originalCanvasData); // 灰度化
	allTwoValue(canvasData); // 使用全局二值化算法
	const valueDataArray = new ImageData(canvasData.width, canvasData.height);
	ergodicPixel(canvasData, function (idx, rgba, x, y) {
		valueDataArray.data[idx + 0] = rgba.r;
		valueDataArray.data[idx + 1] = rgba.g;
		valueDataArray.data[idx + 2] = rgba.b;
		valueDataArray.data[idx + 3] = rgba.a;
		// 注释上面四句代码可进行黑色区域内描边
		if (rgba.r === 0) {
			const arount = getAroundPixel(canvasData, x, y);
			for(let i = 0; i < arount.length; i += 1) {
				if (arount[i] && arount[i].r === 255) {
					valueDataArray.data[idx + 0] = 255;
					valueDataArray.data[idx + 1] = 255;
					valueDataArray.data[idx + 2] = 255;
					valueDataArray.data[idx + 3] = 255;
					break;
				}
			}
		}
	});
	ctx.putImageData(valueDataArray, 0, 0);
}

/**
 * 简单模糊算法
 * 		取处理像素及其周围像素rgb值的均值，为处理像素处的rgb值
 */
function simpleBlurClick () {
	const valueDataArray = new ImageData(canvasData.width, canvasData.height);
	ergodicPixel(canvasData, function (idx, rgba, x, y) {
		const around = getAroundPixel(canvasData, x, y, 2);
		let avgR = rgba.r,
				avgG = rgba.g,
				avgB = rgba.b,
				count = 1;
		for(let i = 0; i < around.length; i += 1) {
			if (around[i]) {
				count++;
				avgR += around[i].r;
				avgG += around[i].g
				avgB += around[i].b;
			}
		}
		avgR /= count;
		avgG /= count;
		avgB /= count;
		valueDataArray.data[idx + 0] = avgR;
		valueDataArray.data[idx + 1] = avgG;
		valueDataArray.data[idx + 2] = avgB;
		valueDataArray.data[idx + 3] = rgba.a;
	})
	ctx.putImageData(valueDataArray, 0, 0);
}

/**
 * 自定义算法1
 * 		取周围共9个像素中各通道值最小的为当前像素通道值
 */
function customAlgorithmClick () {
	const valueDataArray = new ImageData(canvasData.width, canvasData.height);
	ergodicPixel(canvasData, function (idx, rgba, x, y) {
		const around = getAroundPixel(canvasData, x, y);
		let minR = rgba.r, minG = rgba.g, minB = rgba.b;
		for(let i = 0; i < around.length; i += 1) {
			if (around[i]) {
				minR = minR < around[i].r ? minR : around[i].r;
				minG = minG < around[i].g ? minG : around[i].g;
				minB = minR < around[i].b ? minB : around[i].b;
			}
		}
		valueDataArray.data[idx + 0] = minR;
		valueDataArray.data[idx + 1] = minG;
		valueDataArray.data[idx + 2] = minB;
		valueDataArray.data[idx + 3] = rgba.a;
	})
	ctx.putImageData(valueDataArray, 0, 0);
}


/* --============================================================================================================================= */
/* --=======================================================卷积计算=============================================================== */
/* --============================================================================================================================= */
let m = [], // 卷积数组
		divisor = 1, // 除数
		offset = 0; // 色彩偏移量
document.querySelector("#convolution-matrix").value = '0,0,0,0,0,0,0,0,0';
document.querySelector("#convolution-divisor").value = divisor;
document.querySelector("#convolution-offset").value = offset;
document.querySelector("#convolution-offset-text").innerText = offset;

// 卷积矩阵输入
function convolutionMatrixChange() {
	let val = this.value.trim();
	m = val.split(',').map(i => +i);
	if (m.length !== 9) {
		alert('错误的卷积矩阵');
	}
}
// 颜色偏移量改变
function convolutionOffsetChange() {
	document.querySelector("#convolution-offset-text").innerText = this.value;
}
// 计算按钮点击处理
function startConvolutionClick() {
	if (m.length !== 9) {
		alert('错误的卷积矩阵');
		return;
	}
	setTimeout(function() {
		divisor = document.querySelector("#convolution-divisor").value;
		offset = document.querySelector("#convolution-offset").value;
		const resImgData = convolutionMatrix(originalCanvasData, m, divisor, offset);
		ctx.putImageData(resImgData, 0, 0);
	}, 0);
}

// 锐化按钮点击处理
function convolutionSharpeningClick() {
	const mtx = '0,-1,0,-1,5,-1,0,1,0';
	document.querySelector("#convolution-matrix").value = mtx;
	m = mtx.split(',');
	startConvolutionClick();
}

// 浮雕按钮点击处理
function convolutionReliefClick() {
	const mtx = '-2,-1,0,-1,1,1,0,1,2';
	document.querySelector("#convolution-matrix").value = mtx;
	m = mtx.split(',');
	startConvolutionClick();
}

// 边缘增强
function convolutionEdgeEnhance() {
	const mtx = '0,0,0,-1,1,0,0,0,0';
	document.querySelector("#convolution-matrix").value = mtx;
	m = mtx.split(',');
	startConvolutionClick();
}

// 边缘检测
function convolutionEdgeEetection() {
	const mtx = '0,1,0,1,-4,1,0,0,0';
	document.querySelector("#convolution-matrix").value = mtx;
	m = mtx.split(',');
	startConvolutionClick();
}

// 索贝尔边缘检测
function sobelEdgeDetectionH() {
	const mtx = '-1,0,1,-2,0,2,-1,0,1';
	document.querySelector("#convolution-matrix").value = mtx;
	m = mtx.split(',');
	startConvolutionClick();
}
function sobelEdgeDetectionV() {
	const mtx = '1,2,1,0,0,0,-1,-2,-1';
	document.querySelector("#convolution-matrix").value = mtx;
	m = mtx.split(',');
	startConvolutionClick();
}

/**
 * 通用函数
 */
// 遍历所有像素对象
function ergodicPixel (imgData, cb) {
	var sumR = 0, sumG = 0, sumB = 0, count = imgData.width * imgData.height;
	for(var x = 0; x < imgData.width; x += 1) {
		for(var y = 0; y < imgData.height; y += 1) {
			var index = x + y * imgData.width; // 图片像素索引
			var idx = index * 4; // 数据数组索引
			var r = imgData.data[idx + 0];
			var g = imgData.data[idx + 1];
			var b = imgData.data[idx + 2];
			var a = imgData.data[idx + 3];
			sumR += r; sumG += g; sumB += b;
			cb(idx, {
				r: r, g: g, b: b, a: a
			}, x, y);
		}
	}
	return {
		avgR: sumR / count,
		avgG: sumG / count,
		avgB: sumB / count
	};
}
// 根据阈值判断并设值
function judgeAndSet (avgValue, T, idx, rgba) {
	if(avgValue < T) { // 暗部
		canvasData.data[idx + 0] = 0;
		canvasData.data[idx + 1] = 0;
		canvasData.data[idx + 2] = 0;
		canvasData.data[idx + 3] = rgba.a;
	} else { // 亮部
		canvasData.data[idx + 0] = 255;
		canvasData.data[idx + 1] = 255;
		canvasData.data[idx + 2] = 255;
		canvasData.data[idx + 3] = rgba.a;
	}
}
// 获取指定下标位置像素的像素值和
function getPixelByIndex (imgData, index) {
	var idx = index * 4;
	return (imgData.data[idx + 0] + imgData.data[idx + 1] + imgData.data[idx + 3]);
}
/**
 * 获取指定图像的指定位置的像素数据
 * @param {CanvasImgData} imgData canvas图像数据
 * @param {Number} x 像素坐标x
 * @param {Number} y 像素坐标y
 */
function getPixel (imgData, x, y) {
	if (x < 0 || y < 0) return null;
	var idx = (x + y * imgData.width) * 4;
	if (imgData.data[idx] || imgData.data[idx] === 0) {
		return {
			r: imgData.data[idx + 0],
			g: imgData.data[idx + 1],
			b: imgData.data[idx + 2],
			a: imgData.data[idx + 3]
		}
	} else {
		return null;
	}
}
/**
 * 获取像素点(x, y)周围像素
 * @param {CanvasImgData} imgData canvas图像数据
 * @param {Number} x 图像坐标x
 * @param {Number} y 图像坐标y
 * @param {Number} radius 数据半径 大于0
 */
function getAroundPixel (imgData, x, y, radius) {
	var around = new Array(9), num = 0;
	const r = typeof radius !== 'number' ? 1 : radius <= 0 ? 1 : radius;
	for(var i = (-1 * r); i < (r + 1); i += 1) {
		for(var j = (-1 * r); j < (r + 1); j += 1) {
			var p = getPixel(imgData, x + i, y + j);
			if(p && (i !== 0 || j !== 0)) {
				around[num] = p;
			} else {
				around[num] = null;
			}
			num++;
		}
	}
	return around;
}
/**
 * YUV色彩控件与RGB色彩空间的转换控制器
 * 	YUV色彩空间是欧洲电视色彩系统采用的编码标准
 */
var YUV_RGB = {
	/**
	 * 将YUV色彩空间转换成RGB色彩空间（两个色度用于表示色相和饱和度）
	 * @param {Number} y 明亮度
	 * @param {Number} u 色度
	 * @param {Number} v 色度
	 */
	YUVtoRGB: function (y, u, v) {
		return {
			r: y + 1.14 * v,
			g: y - .39 * u - .58 * v,
			b: y + 2.03 * u
		}
	},
	/**
	 * 将RGB色彩空间转换成YUV色彩空间
	 * @param {Number} r 红色分量
	 * @param {Number} g 绿色分量
	 * @param {Number} b 蓝色分量
	 */
	RGBtoYUV: function (r, g, b) {
		if(r < 0) r = 0; if (r > 255) r = 255;
		if(g < 0) g = 0; if (g > 255) g = 255;
		if(b < 0) b = 0; if (b > 255) b = 255;
		return {
			y: .299 * r + .587 * g + .114 * b,
			u:-.147 * r - .289 * g + .436 * b,
			v: .615 * r - .515 * g - .100 * b
		}
	}
}
/**
 * H（色相）S（饱和度）V（明度）色彩空间与RGB色彩空间的转换
 * 	HSV是倒圆锥模型的色彩空间，顶点出V=0，H和S无定义（黑色），顶面中心处V=max，H无定义，S=0（白色），边缘处S=100，H与HSL中色相相同
 */
var HSV_RGB = {
	/**
	 * 将HSV色彩空间转换成RGB色彩空间
	 * @param {Number} h 色相 [0, 360)
	 * @param {Number} s 饱和度 [0， 1]
	 * @param {Number} v 明度 [0, 1]
	 */
	HSVtoRGB: function (h, s, v) {
		if(h < 0) h = 0; if(h > 360) throw new Error('h超出范围');
		if(s < 0) s = 0; if(s > 1) s = 1;
		if(v < 0) v = 0; if(v > 1) v = 1;
		var c = s * v,
			x = c * (1 - Math.abs((h / 60) % 2 - 1)),
			m = v - c,
			r1 = 0, g1 = 0, b1 = 0;
		if(h >= 0 && h < 60) {
			r1 = c; g1 = x; b1 = 0;
		} else if (h >= 60 && h < 120) {
			r1 = x; g1 = c; b1 = 0;
		} else if (h >= 120 && h < 180) {
			r1 = 0; g1 = c; b1 = x;
		} else if (h >= 180 && h < 240) {
			r1 = 0; g1 = x; b1 = c;
		} else if (h >= 240 && h < 300) {
			r1 = x; g1 = 0; b1 = c;
		} else if (h >= 300 && h < 360) {
			r1 = c; g1 = 0; b1 = x;
		}
		return {
			r: Math.round((r1 + m) * 255),
			g: Math.round((g1 + m) * 255),
			b: Math.round((b1 + m) * 255)
		}
	},
	/**
	 * 将RGB色彩空间转换成HSV色彩空间
	 * @param {Number} r 红色分量
	 * @param {Number} g 绿色分量
	 * @param {Number} b 蓝色分量
	 */
	RGBtoHSV: function (r, g, b) {
		if(r < 0) r = 0; if (r > 255) r = 255;
		if(g < 0) g = 0; if (g > 255) g = 255;
		if(b < 0) b = 0; if (b > 255) b = 255;
		r = r / 255; g = g / 255; b = b / 255;
		var max, min, h, s, v;
		v = max = Math.max(r, g, b);
		min = Math.min(r, g, b);
		if(max === 0) {
			s = 0;
		} else {
			s = 1 - min / max;
		}
		if(max === min) {
			h = 0;
		} else if (max === r && g >= b) {
			h = 60 * (g - b) / (max - min) + 0;
		} else if (max === r && g < b) {
			h = 60 * (g - b) / (max - min) + 360;
		} else if (max === g) {
			h = 60 * (b - r) / (max - min) + 120;
		} else if (max === b) {
			h = 60 * (r - g) / (max - min) + 240;
		}
		return {
			h: h,
			s: s,
			v: v
		}
	}
}
/**
 * H（色相）S（饱和度）L（亮度）转换为RGB色彩空间
 */
var HSL_RGB = {
	/**
	 * 将HSL色彩空间转换成RGB色彩空间
	 * @param {Number} h 色相 [0, 360)
	 * @param {Number} s 饱和度 [0， 1]
	 * @param {Number} l 亮度 [0, 1]
	 */
	HSLtoRGB: function (h, s, l) {
		if(h < 0) h = 0; if(h > 360) throw new Error('"h" 超出范围')
		if(s < 0) s = 0; if(s > 1) s = 1;
		var r = 0, g = 0, b = 0, q = 0, p = 0, hk = 0, cr = 0, cg = 0, cb = 0;
		if( s === 0 ) {
			r = g = b = l;
		} else {
			if(l < .5) {
				q = l * (1 + s);
			} else if (l >= .5) {
				q = l + s - (l * s);
			}
			p = 2 * l - q;
			hk = h / 360;
			r = hk + (1 / 3);
			g = hk;
			b = hk - (1 / 3);
			if(r < 0) { r += 1; } else if (r > 1) { r -= 1; }
			if(g < 0) { g += 1; } else if (g > 1) { g -= 1; }
			if(b < 0) { b += 1; } else if (b > 1) { b -= 1; }
			cr = getColorRGB(r, p, q);
			cg = getColorRGB(g, p, q);
			cb = getColorRGB(b, p, q);
		}
		return {
			r: Math.round(cr * 255),
			g: Math.round(cg * 255),
			b: Math.round(cb * 255)
		}
		function getColorRGB (tc, p, q) {
			if(tc < (1 / 6)) {
				return p + ((q - p) * 6 * tc);
			} else if (tc >= (1 / 6) && tc < .5) {
				return q;
			} else if (tc >= .5 && tc < (2 / 3)) {
				return p + ((q - p) * 6 * (2 / 3 - tc));
			} else {
				return p;
			}
		}
	},
	/**
	 * 将RGB色彩空间转换成HSV色彩空间
	 * @param {Number} r 红色分量
	 * @param {Number} g 绿色分量
	 * @param {Number} b 蓝色分量
	 */
	RGBtoHSL: function (r, g, b) {
		if(r < 0) r = 0; if (r > 255) r = 255;
		if(g < 0) g = 0; if (g > 255) g = 255;
		if(b < 0) b = 0; if (b > 255) b = 255;
		r = r / 255; g = g / 255; b = b / 255;
		var max, min, h, s, l;
		max = Math.max(r, g, b);
		min = Math.min(r, g, b);
		l = .5 * (max + min);
		if(l === 0 || max === min) {
			s = 0;
		} else if (l > 0 && l < .5) {
			s = (max - min) / (2 * l);
		} else if (l > .5) {
			s = (max - min) / (2 - 2 * l);
		}
		if(max === min) {
			h = 0;
		} else if (max === r && g >= b) {
			h = 60 * (g - b) / (max - min) + 0;
		} else if (max === r && g < b) {
			h = 60 * (g - b) / (max - min) + 360;
		} else if (max === g) {
			h = 60 * (b - r) / (max - min) + 120;
		} else if (max === b) {
			h = 60 * (r - g) / (max - min) + 240;
		}
		return {
			h: h,
			s: s,
			l: l
		}
	}
}
/**
 * Lab色彩空间与RGB色彩空间的转换
 * 	Lab色彩空间是一种与设备无关的色彩空间，它的创立是基于生物特性系统的，这意味着Lab色彩空间是在以数字化方法描述肉眼可见的色彩信息
 * 	Lab色彩空间比RGB、HSL、CMYK等常见数字化色彩空间能描述更多的色彩。
 * 	L（明度，[0, 100]），a（从洋红到深绿，[-128, 127]），b（描述从黄色到蓝色，[-128, 127]）
 * 	Lab与rgb间的转化必须依赖一个中间色彩空间xyz
 */
var LAB_RGB = {
	param_13: 1 / 3,
	param_16116: 16 / 116,
	Xn: .950456,
	Yn: 1,
	Zn: 1.088754,
	// RGB转换到中间色彩空间XYZ
	RGB2XYZ: function (r, g, b) {
		return {
			x: 0.412453 * r + 0.357580 * g + 0.180423 * b,
			y: 0.212671 * r + 0.715160 * g + 0.072169 * b,
			z: 0.019334 * r + 0.119193 * g + 0.950227 * b
		}
	},
	// 将XYZ色彩空间转换为LAB
	XYZ2Lab: function (x, y, z) {
		var fx, fy, fz, l, a, b;
		x /= (255 * this.Xn);
		y /= ( 255 * this.Yn);
		z /= ( 255 * this.Zn);
		// 计算L
		if(y > .008856) {
			fy = Math.pow(y, this.param_13);
			l = 116 * fy - 16;
		} else {
			fy = 7.787 * y + this.param_16116;
			l = 903.3 * fy;
		}
		l = l > 0 ? l : 0;
		// 计算A B
		if(x > 0.008856) {
			fx = Math.pow(x, this.param_13);
		} else {
			fx = 7.787 * x + this.param_16116;
		}
		if(z > .008856) {
			fz = Math.pow(z, this.param_13);
		} else {
			fz = 7.787 * z + this.param_16116;
		}
		a = 500 * (fx - fy);
		b = 200 * (fy - fz);
		return {
			l: l,
			a: a,
			b: b
		}
	},
	// 将RGB色彩空间转为Lab色彩空间
	RGB2Lab: function (r, g, b) {
		if(r < 0) { r = 0; } else if (r > 255) { r = 255 };
		if(g < 0) { g = 0; } else if (g > 255) { g = 255 };
		if(b < 0) { b = 0; } else if (b > 255) { b = 255 };
		var tempXYZ = this.RGB2XYZ(r, g, b);
		return this.XYZ2Lab(tempXYZ.x, tempXYZ.y, tempXYZ.z);
	},
	// XYZ中间色彩空间转化为RGB色彩空间
	XYZ2RGB: function (x, y, z) {
		var rr, gg, bb;
		rr = 3.240479 * x - 1.537150 * y - 0.498535 * z;
		gg = -0.969256 * x + 1.875992 * y + 0.041556 * z;
		bb = 0.055648 * x - 0.204043 * y + 1.057311 * z;
		if(rr < 0) {
			rr = 0;
		} else if (rr > 255) {
			rr = 255;
		} else {
			rr = Math.round(rr);
		}
		if(gg < 0) {
			gg = 0;
		} else if (gg > 255) {
			gg = 255;
		} else {
			gg = Math.round(gg);
		}
		if(bb < 0) {
			bb = 0;
		} else if (bb > 255) {
			bb = 255;
		} else {
			bb = Math.round(bb);
		}
		return {
			r: rr,
			g: gg,
			b: bb
		}
	},
	// Lab色彩空间转化为XYZ色彩空间
	Lab2XYZ: function (l, a, b) {
		var fx, fy, fz, x, y, z;
		fy = (l + 16) / 116;
		if(fy > .206893) {
			y = fy * fy * fy;
		} else {
			y = (fy - this.param_16116) / 7.787;
		}
		fx = a / 500 + fy;
		if(fx > .206893) {
			x = Math.pow(fx, 3);
		} else {
			x = (fx - this.param_16116) / 7.787;
		}
		fz = fy - b / 200;
		if(fz > .206893) {
			z = Math.pow(fz, 3);
		} else {
			z = (fz - this.param_16116) / 7.787;
		}
		return {
			x: x * .950456 * 255,
			y: y * 255 * 1,
			z: z * 1.088754 * 255
		}
	},
	Lab2RGB: function (l, a, b) {
		var tempXYZ = this.Lab2XYZ(l, a, b);
		return this.XYZ2RGB(tempXYZ.x, tempXYZ.y, tempXYZ.z);
	}
}


/**
 * 对所有按钮进行事件绑定
 */
function bindEvents () {
	// 原图按钮
	document.getElementById('Original').addEventListener('click', originalBtnClick, false);

	// 加权灰度化按钮
	document.getElementById('weightingGrayscale').addEventListener('click', weightingGrayscaleBtnClick, false);
	// R分量灰度化按钮
	document.getElementById('rWeightGrayscale').addEventListener('click', rWeightGrayscaleBtnClick, false);
	// G分量灰度化
	document.getElementById('gWeightGrayscale').addEventListener('click', gWeightGrayscaleBtnClick, false);
	// B分量灰度化
	document.getElementById('bWeightGrayscale').addEventListener('click', bWeightGrayscaleBtnClick, false);
	// 最大值灰度化
	document.getElementById('maxWeightGrayscale').addEventListener('click', maxWeightGrayscaleBtnClick, false);
	// 平均值灰度化
	document.getElementById('avgGrayscale').addEventListener('click', avgGrayscaleBtnClick, false);

	// 旋转按钮点击处理
	document.getElementById('rotate').addEventListener('click', rotateBtnClick, false);
	// 反色按钮点击处理
	document.getElementById("anticolor").addEventListener('click', anticolorBtnClick, false);

	// 全局二值化（固定阈值）
	document.getElementById('avgTwoValue').addEventListener('click', avgTwoValueBtnClick, false);
	// 全局二值化（均值阈值）
	document.getElementById('allTwoValue').addEventListener('click', allTwoValueBtnClick, false);
	// 自适应二值化（十字交叉阈值）
	document.getElementById('crossTwoValue').addEventListener('click', crossTwoValueBtnClick, false);
	
	// 亮度范围条拖动处理
	document.getElementById('brightness').addEventListener('change', handleBrightnessRange, false);
	// 饱和度范围条
	document.getElementById('saturation').addEventListener('change', saturationBarChange, false);
	// 对比度范围条
	document.getElementById('contrastRatio').addEventListener('change', contrastRatioBarChange, false);

	// 简单膨胀算法按钮
	document.getElementById('simple-expand').addEventListener('click', simpleExpandBtnClick, false);
	// 简单腐蚀算法按钮
	document.getElementById('simple-corrosion').addEventListener('click', simpleCorrosionClick, false);
	// 简单的模糊算法那
	document.getElementById('simple-blur').addEventListener('click', simpleBlurClick, false);

	// 自定义的算法1
	document.getElementById('custom-algorithm').addEventListener('click', customAlgorithmClick, false);

	// 卷积数组输入
	document.getElementById('convolution-matrix').addEventListener('change', convolutionMatrixChange, false);
	// 颜色偏移量输入
	document.getElementById('convolution-offset').addEventListener('change', convolutionOffsetChange, false);
	// 卷积算计按钮点击
	document.getElementById('start-convolution').addEventListener('click', startConvolutionClick, false);
	// 锐化按钮点击处理
	document.getElementById('convolution-sharpening').addEventListener('click', convolutionSharpeningClick, false);
	// 浮雕按钮点击
	document.getElementById('convolution-relief').addEventListener('click', convolutionReliefClick, false);
	// 边缘增强
	document.getElementById('convolution-edge-enhance').addEventListener('click', convolutionEdgeEnhance, false);
	// 边缘检测
	document.getElementById('convolution-edge-detection').addEventListener('click', convolutionEdgeEetection, false);
	// 索贝尔边缘检测
	document.getElementById('sobel-edge-detection-h').addEventListener('click', sobelEdgeDetectionH, false);
	document.getElementById('sobel-edge-detection-v').addEventListener('click', sobelEdgeDetectionV, false);
}

bindEvents();